﻿using System;
using System.Collections.Generic;
// 4.6.4 迭代器语义
// 迭代器是包含一个或多个yield语句的方法、属性或索引器，迭代器必须返回以下四个接口之一（否则，编译器会报错）：
// Enumerable接口
//System.Collections.IEnumerable
//System.Collections.Generic.IEnumerable<T>
// Enumerator接口
//System.Collections.IEnumerator;
//System.Collections.Generic.IEnumerator<T>;
// 返回enumerable接口和返回enumerator接口的迭代器具有不同的语义。

namespace _111_枚举类型的迭代_迭代器语义
{
    class Test
    {
        // 允许使用多个yield语句，例如：
        static IEnumerable<string> Foo()
        {
            yield return "One";
            yield return "Two";
            yield return "Three";
        }
        //1.yield break语句
        //yield break语句表明迭代器不返回后面的元素而是提前结束。
        static IEnumerable<string> Foo(bool breakEarly)
        {
            yield return "One";
            yield return "Two";
            if (breakEarly)
                yield break;
            yield return "Three";
        }
        //提示：迭代器块中使用return语句是不合法的，必须使用yield break语句来代替。
        //2.迭代器和try/catch/finally语句块
        //yield return 语句不能出现在带catch子句的try语句块中：
        /*
        IEnumerable<string> Foo()
        {
            try { yield return "One"; } //不合法
            catch { }
        }
        */

        //yield return 语句也不能出现在catch或finally语句块中。出现这些限制的原因是编译器必须将迭代器转换为带有MoveNext、Current和Dispose成员的普通类，而且转换异常处理语句块可能会大大增加代码复杂性。
        //但是，可以在只带有finally语句块try块使用yield语句：
        IEnumerable<string> Foo1()
        {
            try { yield return "One"; } // 合法
            finally { }
        }
        //当枚举类型到达队列末尾或者处理完毕时，finally块就可以执行了。如果提前使用break中断循环，那么foreach语句会显式地结束枚举器，这是一种正确使用枚举器的方法。
        //在以显式方式处理枚举器时，我们可以使用一个标记来提前放弃枚举，而不需要结束它，从而绕过finally语句块，可以将显式的枚举封装到一个using语句中，从而规避这个风险。
        static void Main(string[] args)
        {
            foreach(string s in Foo())
            {
                Console.WriteLine(s); // 打印 "One", "Two", "Three"
            }

            //当枚举类型到达队列末尾或者处理完毕时，finally块就可以执行了。如果提前使用break中断循环，那么foreach语句会显式地结束枚举器，这是一种正确使用枚举器的方法。
            //在以显式方式处理枚举器时，我们可以使用一个标记来提前放弃枚举，而不需要结束它，从而绕过finally语句块，可以将显式的枚举封装到一个using语句中，从而规避这个风险。
            string firstElement = null;
            var sequence = Foo();
            using (var enumerator = sequence.GetEnumerator())
                if (enumerator.MoveNext())
                    firstElement = enumerator.Current;
            Console.ReadKey();
        }
    }
}
